Flowable 自定义表达式
适用版本:PIGX 5.10 及以上
流程引擎:Flowable
核心模块:flow-biz(业务处理模块)
最后更新:2025 年 10 月
第一部分:表达式基础
1.1 表达式系统概述
表达式 是 Flowable 流程定义中用于动态处理业务逻辑的核心机制,通过表达式可以:
- 动态确定任务审批人
- 实现复杂的分支条件判断
- 访问和修改流程变量
- 调用 Spring Bean 方法
- 整合业务系统数据
1.2 表达式执行架构
流程定义配置
↓
表达式解析(Expression Parser)
↓
Spring Bean 定位
↓
方法调用(使用 DelegateExecution)
↓
返回结果(集合 / 布尔值 / 对象)
↓
流程引擎处理
↓
流程节点分发 / 分支判断
1.3 核心概念
| 概念 | 定义 | 示例 |
|---|
| Spring Bean | 流程引擎可调用的 Spring 容器托管对象 | demoBean |
| Expression | 表达式字符串 | demoBean.getUserList(execution) |
| DelegateExecution | Flowable 执行上下文,携带流程信息 | 方法参数 |
| 变量域 | 流程级别、任务级别、局部级别变量存储 | 全局变量池 |
第二部分:表达式语法详解
2.1 表达式语法规范
基本语法结构
springBeanName.methodName(execution[, otherParams])
| 组成部分 | 类型 | 必需 | 说明 |
|---|
springBeanName | 标识符 | ✅ | Spring 容器中 Bean 的名称(@Component 注解中定义) |
. | 操作符 | ✅ | 对象成员访问符 |
methodName | 标识符 | ✅ | Bean 中的公开方法名称 |
(execution) | 参数 | ✅ | 必须传入 DelegateExecution 对象 |
[, otherParams] | 参数 | ❌ | 可选的额外参数(PIGX 版本需支持) |
语法示例
| 场景 | 表达式 | 返回类型 |
|---|
| 获取审批人 | demoBean.getUserList(execution) | List<Long> |
| 条件判断 | demoBean.switchUser(execution) | boolean |
| 参数传递 | demoBean.getApprover(execution, "manager") | List<Long> |
| 复杂逻辑 | demoBean.validateAndGetUsers(execution) | List<Long> |
2.2 保留字和关键字
| 关键字 | 用途 | 示例 |
|---|
execution | 固定参数,代表流程执行上下文 | 必须使用 |
${...} | 变量表达式(某些版本支持) | ${processInstanceId} |
#{...} | SpEL 表达式(某些版本支持) | #{T(System).currentTimeMillis()} |
⚠安全建议
PIGX 主要使用 Spring Bean 方法调用,避免在表达式中直接使用 SpEL,以保持系统安全性和可维护性。
2.3 表达式验证规则
| 规则 | 说明 | 违反后果 |
|---|
| Bean 必须存在 | 表达式中的 Bean 名称必须在 Spring 容器中注册 | 流程执行报错 |
| 方法必须公开 | 调用的方法必须是 public 访问级别 | 运行时异常 |
| 参数类型必须匹配 | 传入的参数类型必须与方法签名一致 | 类型转换错误 |
| 返回类型受限 | 审批人表达式返回 List<Long>,条件表达式返回 boolean | 流程节点处理失败 |
第三部分:表达式类型详解
3.1 审批人表达式 (Assignee Expression)
定义与用途
审批人表达式 用于在流程执行时动态确定任务的处理人(审批人、执行人)。
| 属性 | 值 |
|---|
| 返回类型 | List<Long> 或 List<String>(用户 ID 列表) |
| 执行时机 | 流程到达用户任务节点时 |
| 作用范围 | 单个任务节点 |
| 使用场景 | 根据业务规则动态分配审批人 |
表达式配置
流程定义配置方式
在 Flowable 流程设计器中,为用户任务节点配置审批人表达式:
节点类型: User Task
表达式字段: Assignee Expression 或 Assignee
表达式内容: demoBean.getUserList(execution)
多人审批配置 (根据 PIGX 实现)
表达式: demoBean.getMultipleApprovers(execution)
返回值: [1, 2, 3] (多个用户 ID)
处理方式: 并行 / 串联审批
核心实现
标准审批人 Bean
@Component("demoBean")
public class DemoBean {
private final RemoteUserService remoteUserService;
public DemoBean(RemoteUserService remoteUserService) {
this.remoteUserService = remoteUserService;
}
/**
* 获取单个审批人
* @param execution 委托执行对象
* @return 审批人 ID 列表
*/
public List<Long> getUserList(DelegateExecution execution) {
// 获取流程变量
Object userId = execution.getVariable("userId");
// 业务逻辑处理
Long approver = determineApprover(userId);
return Arrays.asList(approver);
}
/**
* 获取多个审批人(并行审批)
* @param execution 委托执行对象
* @return 审批人 ID 列表
*/
public List<Long> getMultipleApprovers(DelegateExecution execution) {
// 获取当前用户部门
Long deptId = (Long) execution.getVariable("deptId");
// 查询部门经理和总经理
List<Long> managers = remoteUserService.getManagersByDept(deptId);
return managers;
}
/**
* 获取具有特定角色的用户
* @param execution 委托执行对象
* @return 用户 ID 列表
*/
public List<Long> getUsersByRole(DelegateExecution execution) {
String role = (String) execution.getVariable("requiredRole");
List<Long> users = remoteUserService.getUsersByRole(role);
return users;
}
private Long determineApprover(Object userId) {
// 实现具体的审批人确定逻辑
return 1L;
}
}
基于业务数据的审批人
@Component("businessBean")
public class BusinessBean {
@Autowired
private BpmOaLeaveService leaveService;
/**
* 根据请假天数确定审批人
* @param execution 委托执行对象
* @return 审批人 ID
*/
public List<Long> getApproverByLeaveDay(DelegateExecution execution) {
String processInstanceId = execution.getProcessInstanceId();
// 查询业务数据
BpmOaLeaveEntity leave = leaveService.getOne(Wrappers.<BpmOaLeaveEntity>lambdaQuery()
.eq(BpmOaLeaveEntity::getProcessInstanceId, processInstanceId));
// 根据请假天数返回不同的审批人
if (leave.getLeaveDay() <= 3) {
// 直属主管审批
return Arrays.asList(leave.getUserDeptManager());
} else if (leave.getLeaveDay() <= 7) {
// 部门总经理审批
return Arrays.asList(leave.getDeptDirector());
} else {
// 需要 HR 和总经理联合审批
return Arrays.asList(
leave.getDeptDirector(),
leave.getHrManager()
);
}
}
}
DelegateExecution 对象 API
| 方法 | 返回类型 | 说明 | 示例 |
|---|
getVariable(name) | Object | 获取流程变量 | execution.getVariable("userId") |
setVariable(name, value) | void | 设置流程变量 | execution.setVariable("status", "approved") |
getProcessInstanceId() | String | 获取流程实例 ID | String pid = execution.getProcessInstanceId() |
getProcessDefinitionId() | String | 获取流程定义 ID | String defId = execution.getProcessDefinitionId() |
getVariables() | Map<String, Object> | 获取所有变量 | Map vars = execution.getVariables() |
3.2 条件判断表达式 (Condition Expression)
定义与用途
条件判断表达式 用于在流程分支中实现条件路由,根据条件返回值决定流程走向。
| 属性 | 值 |
|---|
| 返回类型 | boolean(true / false) |
| 执行时机 | 流程经过排他网关(Exclusive Gateway)或条件边时 |
| 作用范围 | 单条流程分支 |
| 使用场景 | 流程分支判断、业务规则路由 |
表达式配置
流程定义配置方式
在排他网关或条件边上配置条件表达式:
元素类型: Exclusive Gateway / Sequence Flow
表达式字段: Condition Expression
表达式内容: demoBean.switchUser(execution)
多分支配置示例
网关分支 1: demoBean.isApproved(execution) → 流向通过
网关分支 2: demoBean.isRejected(execution) → 流向拒绝
网关默认路: (无条件) → 默认流向
核心实现
标准条件判断
@Component("conditionBean")
public class ConditionBean {
@Autowired
private BpmOaLeaveService leaveService;
/**
* 审批通过条件
* @param execution 委托执行对象
* @return true 表示通过,false 表示未通过
*/
public boolean isApproved(DelegateExecution execution) {
// 获取审批意见
String opinion = (String) execution.getVariable("opinion");
// 返回条件判断结果
return "approved".equals(opinion);
}
/**
* 审批拒绝条件
* @param execution 委托执行对象
* @return true 表示拒绝
*/
public boolean isRejected(DelegateExecution execution) {
String opinion = (String) execution.getVariable("opinion");
return "rejected".equals(opinion);
}
/**
* 复杂业务条件:根据请假天数判断是否需要总经理审批
* @param execution 委托执行对象
* @return true 需要二级审批
*/
public boolean needsSecondaryApproval(DelegateExecution execution) {
String processInstanceId = execution.getProcessInstanceId();
// 查询业务数据
BpmOaLeaveEntity leave = leaveService.getOne(Wrappers.<BpmOaLeaveEntity>lambdaQuery()
.eq(BpmOaLeaveEntity::getProcessInstanceId, processInstanceId));
// 超过 7 天需要总经理审批
return leave.getLeaveDay() > 7;
}
/**
* 金额判断条件
* @param execution 委托执行对象
* @return true 金额超出预算
*/
public boolean isAmountExceeded(DelegateExecution execution) {
Double amount = (Double) execution.getVariable("amount");
Double budget = (Double) execution.getVariable("budget");
return amount != null && budget != null && amount > budget;
}
}
基于流程变量的条件
@Component("variableCondition")
public class VariableCondition {
/**
* 根据申请人等级判断审批流程
* @param execution 委托执行对象
* @return true 高管流程
*/
public boolean isExecutiveApprovalRequired(DelegateExecution execution) {
String userLevel = (String) execution.getVariable("userLevel");
return "executive".equals(userLevel);
}
/**
* 根据部门判断审批权限
* @param execution 委托执行对象
* @return true 需要财务部审批
*/
public boolean needsFinanceApproval(DelegateExecution execution) {
String dept = (String) execution.getVariable("department");
return "sales".equals(dept) || "purchase".equals(dept);
}
/**
* 时间区间判断
* @param execution 委托执行对象
* @return true 工作时间外
*/
public boolean isOutsideWorkingHours(DelegateExecution execution) {
LocalDateTime now = LocalDateTime.now();
int hour = now.getHour();
// 判断是否在工作时间外(18:00 - 09:00)
return hour < 9 || hour >= 18;
}
}
链式条件判断
@Component("chainCondition")
public class ChainCondition {
@Autowired
private BpmOaLeaveService leaveService;
/**
* 综合条件判断链
* @param execution 委托执行对象
* @return true 满足所有条件
*/
public boolean validateAll(DelegateExecution execution) {
// 条件 1: 检查是否有重复申请
if (hasDuplicateRequest(execution)) {
return false;
}
// 条件 2: 检查余额
if (!hasEnoughBalance(execution)) {
return false;
}
// 条件 3: 检查时间冲突
if (hasTimeConflict(execution)) {
return false;
}
return true;
}
private boolean hasDuplicateRequest(DelegateExecution execution) {
// 实现检查逻辑
return false;
}
private boolean hasEnoughBalance(DelegateExecution execution) {
// 实现余额检查逻辑
return true;
}
private boolean hasTimeConflict(DelegateExecution execution) {
// 实现时间冲突检查逻辑
return false;
}
}
条件返回值说明
| 返回值 | 含义 | 流程行为 |
|---|
true | 条件成立 | 沿当前分支继续执行 |
false | 条件不成立 | 尝试其他分支或默认分支 |
| 异常 | 条件判断错误 | 流程中断,记录异常 |
null | 无返回值 | 作为 false 处理 |
第四部分:流程数据交互
4.1 流程变量系统
变量类型
| 变量类型 | 存储位置 | 生命周期 | 访问范围 |
|---|
| 流程级变量 | 流程实例中 | 流程启动 → 流程完成 | 全流程可见 |
| 任务级变量 | 任务实例中 | 任务创建 → 任务完成 | 当前任务 + 委托 |
| 局部变量 | 执行分支中 | 分支创建 → 分支完成 | 当前分支 |
变量访问方式
// 获取变量
Object value = execution.getVariable("variableName");
// 设置变量
execution.setVariable("variableName", "value");
// 获取所有变量
Map<String, Object> allVariables = execution.getVariables();
// 删除变量
execution.removeVariable("variableName");
// 检查变量是否存在
boolean exists = execution.hasVariable("variableName");
4.2 流程实例数据获取
通过 processInstanceId 查询数据
获取当前状态
| 信息 | API / 方法 | 返回类型 |
|---|
| 实例状态 | execution.getProcessInstanceId() | String |
| 当前活动节点 | execution.getCurrentActivityId() | String |
| 流程变量 | execution.getVariable(name) | Object |
| 所有变量 | execution.getVariables() | Map |
查询历史记录
@Component("historyBean")
public class HistoryBean {
@Autowired
private HistoryService historyService;
/**
* 获取流程完整历史
* @param processInstanceId 流程实例 ID
* @return 历史实例列表
*/
public List<HistoricProcessInstance> getProcessHistory(String processInstanceId) {
return historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.list();
}
/**
* 获取任务历史
* @param processInstanceId 流程实例 ID
* @return 历史任务列表
*/
public List<HistoricTaskInstance> getTaskHistory(String processInstanceId) {
return historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceEndTime()
.asc()
.list();
}
/**
* 获取流程变量历史
* @param processInstanceId 流程实例 ID
* @return 变量历史列表
*/
public List<HistoricVariableInstance> getVariableHistory(String processInstanceId) {
return historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
}
}
4.3 业务数据与流程数据关联
关联模式
业务表 (bpm_oa_leave)
↓
process_instance_id 字段(唯一关键)
↓
流程实例 (Flowable ProcessInstance)
↓
流程变量 (Process Variables)
↓
任务与审批数据
数据查询示例
@Service
public class LeaveProcessService {
@Autowired
private BpmOaLeaveService leaveService;
@Autowired
private RuntimeService runtimeService;
/**
* 根据流程实例 ID 查询完整数据
* @param processInstanceId 流程实例 ID
* @return 完整的业务和流程数据
*/
public Map<String, Object> getCompleteData(String processInstanceId) {
Map<String, Object> result = new HashMap<>();
// 1. 获取业务数据
BpmOaLeaveEntity leave = leaveService.getByProcessInstanceId(processInstanceId);
result.put("business", leave);
// 2. 获取流程实例
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
result.put("instance", instance);
// 3. 获取流程变量
Map<String, Object> variables = runtimeService.getVariables(processInstanceId);
result.put("variables", variables);
return result;
}
/**
* 从流程变量获取业务数据
* @param execution 委托执行对象
* @return 业务实体
*/
public BpmOaLeaveEntity getBusinessDataFromExecution(DelegateExecution execution) {
String processInstanceId = execution.getProcessInstanceId();
return leaveService.getByProcessInstanceId(processInstanceId);
}
}
4.4 表单数据流转
数据流转周期
前端表单 (create.vue)
↓
POST /api/bpmOaLeave (表单数据)
↓
Controller.save()
↓
Service.saveAndStartProcess()
├─ 1. 保存业务数据 → bpm_oa_leave 表
├─ 2. 设置 process_instance_id
└─ 3. 启动流程 → 流程变量初始化
↓
流程运行
├─ 表达式调用
├─ 审批人确定
├─ 任务分配
└─ 流程变量修改
↓
审批操作 (detail.vue)
├─ 获取流程数据
├─ 展示业务信息
├─ 提交审批意见
└─ 更新 leave_status
↓
流程完成
├─ 触发状态回调
├─ 更新业务表数据
└─ 归档流程数据
关键数据转换点
| 阶段 | 数据来源 | 数据转换 | 数据目标 |
|---|
| 启动 | create.vue 表单 | 序列化为 Java 对象 | bpm_oa_leave 表 |
| 初始化 | 业务表数据 | 转换为流程变量 | ProcessInstance |
| 审批 | 任务实例 | 表达式调用业务数据 | 审批人确定 |
| 完成 | 流程状态 | 状态值映射 | leave_status 更新 |
第五部分:表达式最佳实践
5.1 表达式设计原则
✅ 必须遵循的原则
| 原则 | 说明 | 示例 |
|---|
| 单一职责 | 一个表达式只处理一个业务逻辑 | ❌ 不要在审批人表达式中做条件判断 |
| 幂等性 | 相同输入必须产生相同输出 | ✅ 表达式不应依赖随机数或时间 |
| 快速执行 | 避免耗时操作(数据库查询应有缓存) | ❌ 不要在表达式中执行复杂计算 |
| 异常处理 | 表达式应优雅处理异常 | ✅ 使用 try-catch 和日志记录 |
| 可维护性 | 表达式代码应清晰易懂,有适当注释 | ✅ 方法名应表达业务含义 |
5.2 表达式编写规范
命名规范
// 审批人表达式方法命名规范
public List<Long> get[业务名称]Approvers(DelegateExecution execution)
public List<Long> get[级别]Users(DelegateExecution execution)
public List<Long> getUsersBy[条件](DelegateExecution execution)
// 示例
public List<Long> getLeaveApprovers(DelegateExecution execution)
public List<Long> getManagerUsers(DelegateExecution execution)
public List<Long> getUsersByDepartment(DelegateExecution execution)
// 条件判断方法命名规范
public boolean is[条件状态](DelegateExecution execution)
public boolean needs[动作](DelegateExecution execution)
public boolean can[操作](DelegateExecution execution)
// 示例
public boolean isApproved(DelegateExecution execution)
public boolean needsSecondaryApproval(DelegateExecution execution)
public boolean canApprove(DelegateExecution execution)
异常处理
@Component("safeBean")
public class SafeBean {
private static final Logger log = LoggerFactory.getLogger(SafeBean.class);
/**
* 安全的审批人获取方法
* @param execution 委托执行对象
* @return 审批人列表(默认空列表)
*/
public List<Long> getSafeApprovers(DelegateExecution execution) {
try {
// 获取数据和处理逻辑
String processInstanceId = execution.getProcessInstanceId();
// ... 业务逻辑
return Arrays.asList(1L, 2L);
} catch (NullPointerException e) {
log.error("NPE in getSafeApprovers: {}", e.getMessage());
return Collections.emptyList();
} catch (Exception e) {
log.error("Error in getSafeApprovers", e);
// 返回默认审批人或抛出有意义的异常
return Arrays.asList(0L); // 0L 表示系统管理员
}
}
/**
* 安全的条件判断
* @param execution 委托执行对象
* @return 条件结果(异常时默认 false)
*/
public boolean safeCondition(DelegateExecution execution) {
try {
// 条件判断逻辑
return true;
} catch (Exception e) {
log.error("Error in safeCondition", e);
return false; // 异常时安全地返回 false
}
}
}
5.3 表达式测试
单元测试示例
@SpringBootTest
public class ExpressionBeanTest {
@Autowired
private DemoBean demoBean;
private DelegateExecution execution;
@Before
public void setUp() {
execution = Mockito.mock(DelegateExecution.class);
}
@Test
public void testGetUserList() {
// 安排
Mockito.when(execution.getVariable("userId")).thenReturn(123L);
// 执行
List<Long> result = demoBean.getUserList(execution);
// 断言
Assert.assertNotNull(result);
Assert.assertFalse(result.isEmpty());
}
@Test
public void testNeedsSecondaryApproval() {
// 测试需要二级审批的情况
Mockito.when(execution.getVariable("deptId")).thenReturn(1L);
boolean result = demoBean.needsSecondaryApproval(execution);
Assert.assertTrue(result);
}
}
第六部分:常见表达式场景
6.1 请假审批流程
场景描述
根据请假天数和申请人等级,实现分级审批流程。
表达式实现
@Component("leaveBean")
public class LeaveBean {
@Autowired
private BpmOaLeaveService leaveService;
/**
* 根据请假天数获取审批人
*/
public List<Long> getLeaveApprover(DelegateExecution execution) {
BpmOaLeaveEntity leave = getLeaveData(execution);
// 根据请假天数分级审批
if (leave.getLeaveDay() <= 3) {
return Arrays.asList(leave.getDirectManagerId());
} else if (leave.getLeaveDay() <= 7) {
return Arrays.asList(leave.getDeptDirectorId());
} else {
return Arrays.asList(
leave.getDeptDirectorId(),
leave.getHrManagerId()
);
}
}
/**
* 判断是否需要 HR 审批
*/
public boolean needsHrApproval(DelegateExecution execution) {
BpmOaLeaveEntity leave = getLeaveData(execution);
return leave.getLeaveDay() > 7 || "special".equals(leave.getLeaveType());
}
private BpmOaLeaveEntity getLeaveData(DelegateExecution execution) {
String processInstanceId = execution.getProcessInstanceId();
return leaveService.getByProcessInstanceId(processInstanceId);
}
}
流程设计
开始
↓
[请假申请] → 保存数据、启动流程
↓
≪分支1: 3天以内≫ → [直属主管审批]
↓
≪分支2: 3-7天≫ → [部门总经理审批]
↓
≪分支3: 超过7天≫ → [部门总经理审批] → [HR审批]
↓
{Approved? → 是: [流程完成] → 否: [驳回]}
↓
结束
6.2 采购审批流程
场景描述
根据采购金额和类型,实现多级审批。
@Component("purchaseBean")
public class PurchaseBean {
@Autowired
private BpmOaPurchaseService purchaseService;
/**
* 根据金额获取审批人
*/
public List<Long> getPurchaseApprover(DelegateExecution execution) {
Map<String, Object> vars = execution.getVariables();
Double amount = (Double) vars.get("amount");
List<Long> approvers = new ArrayList<>();
// 金额分级
if (amount <= 1000) {
approvers.add((Long) vars.get("deptManagerId"));
} else if (amount <= 10000) {
approvers.add((Long) vars.get("deptDirectorId"));
} else if (amount <= 100000) {
approvers.addAll(Arrays.asList(
(Long) vars.get("financeManagerId"),
(Long) vars.get("executiveDirectorId")
));
} else {
approvers.addAll(Arrays.asList(
(Long) vars.get("cfoId"),
(Long) vars.get("ceoId")
));
}
return approvers;
}
/**
* 判断是否需要政府部门审批
*/
public boolean needsGovernmentApproval(DelegateExecution execution) {
String category = (String) execution.getVariable("category");
return "government_contract".equals(category);
}
}
6.3 报销流程
场景描述
员工报销,根据金额和类别实现动态审批。
@Component("expenseBean")
public class ExpenseBean {
@Autowired
private BpmExpenseService expenseService;
/**
* 获取报销审批人
*/
public List<Long> getExpenseApprover(DelegateExecution execution) {
String expenseId = (String) execution.getVariable("expenseId");
BpmExpenseEntity expense = expenseService.getById(expenseId);
List<Long> approvers = new ArrayList<>();
// 第一级:直属主管(所有金额都需要)
approvers.add(expense.getDirectManagerId());
// 第二级:部门经理(超过2000元)
if (expense.getAmount() > 2000) {
approvers.add(expense.getDeptManagerId());
}
// 第三级:财务负责人(超过5000元)
if (expense.getAmount() > 5000) {
approvers.add(expense.getFinanceManagerId());
}
return approvers;
}
/**
* 判断是否需要特殊审批
*/
public boolean needsSpecialApproval(DelegateExecution execution) {
String category = (String) execution.getVariable("category");
Double amount = (Double) execution.getVariable("amount");
// 特殊项目或大金额需要特殊审批
return "special_project".equals(category) && amount > 10000;
}
}
第七部分:故障排查与调试
7.1 常见错误与解决方案
错误 1:Bean 名称错误
| 症状 | 原因 | 排查步骤 | 解决方案 |
|---|
| 流程无法启动 | Bean 不存在或名称错误 | 1. 检查 @Component 注解名称 2. 确认 Spring 自动扫描配置 | 确保 Bean 名称与表达式一致 |
异常:NoSuchBeanDefinitionException | 容器中找不到 Bean | 查看应用启动日志 | 检查 Spring 扫描路径配置 |
错误 2:方法返回类型不匹配
| 症状 | 原因 | 排查步骤 | 解决方案 |
|---|
| 任务无审批人分配 | 返回值类型错误 | 1. 检查方法签名 2. 验证返回类型 | 审批人方法必须返回 List<Long> |
| 流程分支无法选择 | 条件方法返回非布尔值 | 1. 检查返回值 2. 调试表达式 | 条件方法必须返回 boolean |
错误 3:流程变量未初始化
| 症状 | 原因 | 排查步骤 | 解决方案 |
|---|
| 表达式中 getVariable 返回 null | 变量未设置或命名不一致 | 1. 检查变量名称 2. 查看流程启动代码 | 确保在启动流程前设置所有变量 |
| NullPointerException | 获取 null 变量后直接使用 | 添加 null 检查和异常处理 | 在表达式中加入防御性编程 |
7.2 调试技巧
启用详细日志
# application.yml
logging:
level:
org.flowable: DEBUG
com.pigx.bpm: DEBUG
# logback-spring.xml
<logger name="org.flowable.engine.impl.el" level="DEBUG"/>
<logger name="org.flowable.engine.impl.bpmn.behavior" level="DEBUG"/>
表达式调试代码
@Component("debugBean")
public class DebugBean {
private static final Logger log = LoggerFactory.getLogger(DebugBean.class);
/**
* 调试表达式执行
*/
public List<Long> debugGetApprovers(DelegateExecution execution) {
log.info("=== Expression Debug Start ===");
log.info("ProcessInstanceId: {}", execution.getProcessInstanceId());
log.info("Variables: {}", execution.getVariables());
try {
// 业务逻辑
List<Long> result = Arrays.asList(1L, 2L);
log.info("Result: {}", result);
return result;
} catch (Exception e) {
log.error("Expression execution failed", e);
log.info("=== Expression Debug End (ERROR) ===");
throw e;
}
}
}
单步调试
// 在 IDE 中调试表达式执行
@Component("stepDebugBean")
public class StepDebugBean {
public List<Long> stepDebugApprovers(DelegateExecution execution) {
// 在此处设置断点
String processInstanceId = execution.getProcessInstanceId(); // Step 1
Object userId = execution.getVariable("userId"); // Step 2
// 按 F10 逐步执行
List<Long> approvers = determineApprovers(userId); // Step 3
return approvers;
}
private List<Long> determineApprovers(Object userId) {
// 实现审批人确定逻辑
return Arrays.asList(1L);
}
}
7.3 性能监控
表达式性能测试
@Component("performanceBean")
public class PerformanceBean {
private static final Logger log = LoggerFactory.getLogger(PerformanceBean.class);
/**
* 性能监测的审批人获取
*/
public List<Long> getApproversWithMonitoring(DelegateExecution execution) {
long startTime = System.currentTimeMillis();
try {
List<Long> approvers = getApprovers(execution);
return approvers;
} finally {
long duration = System.currentTimeMillis() - startTime;
log.info("Expression execution time: {} ms", duration);
// 如果超过阈值,记录警告
if (duration > 1000) {
log.warn("Expression execution took too long: {} ms", duration);
}
}
}
private List<Long> getApprovers(DelegateExecution execution) {
// 实现审批人获取逻辑
return Arrays.asList(1L);
}
}
第八部分:高级用法
8.1 动态表达式(若支持)
@Component("dynamicBean")
public class DynamicBean {
@Autowired
private ScriptEngineManager scriptEngineManager;
/**
* 动态执行脚本表达式(需要 PIGX 支持)
*/
public List<Long> executeDynamicExpression(DelegateExecution execution) {
String expression = (String) execution.getVariable("dynamicExpression");
// 通过脚本引擎执行动态表达式
// 需要 PIGX 框架支持
return Arrays.asList(1L);
}
}
8.2 参数化表达式
@Component("parameterizedBean")
public class ParameterizedBean {
/**
* 带参数的审批人获取(需要 PIGX 支持参数传递)
*/
public List<Long> getApproversByLevel(DelegateExecution execution, String level) {
if ("high".equals(level)) {
return Arrays.asList(100L); // CEO
} else if ("medium".equals(level)) {
return Arrays.asList(50L); // Manager
} else {
return Arrays.asList(10L); // Staff
}
}
}
8.3 缓存优化
@Component("cachedBean")
public class CachedBean {
@Autowired
private CacheManager cacheManager;
/**
* 带缓存的审批人获取
*/
@Cacheable(value = "approvers", key = "#level")
public List<Long> getCachedApprovers(String level) {
// 实现审批人获取逻辑(结果会被缓存)
return Arrays.asList(1L);
}
/**
* 清除缓存
*/
@CacheEvict(value = "approvers", allEntries = true)
public void clearApproverCache() {
// 清除所有缓存
}
}
第九部分:完整工作流示例
9.1 请假流程完整实现
数据库表
CREATE TABLE `bpm_oa_leave` (
`id` BIGINT PRIMARY KEY,
`process_instance_id` VARCHAR(64),
`username` VARCHAR(255),
`leave_day` SMALLINT,
`leave_status` SMALLINT,
`start_time` DATETIME(6),
`end_time` DATETIME(6),
`create_time` DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)
);
Service Bean
@Component("leaveWorkflowBean")
public class LeaveWorkflowBean {
@Autowired
private BpmOaLeaveService leaveService;
// 审批人表达式
public List<Long> getLeaveApprovers(DelegateExecution execution) {
BpmOaLeaveEntity leave = getLeaveData(execution);
return leave.getLeaveDay() <= 3
? Arrays.asList(leave.getDirectManagerId())
: Arrays.asList(leave.getDeptDirectorId());
}
// 条件表达式:需要 HR 审批
public boolean needsHrApproval(DelegateExecution execution) {
BpmOaLeaveEntity leave = getLeaveData(execution);
return leave.getLeaveDay() > 7;
}
private BpmOaLeaveEntity getLeaveData(DelegateExecution execution) {
return leaveService.getByProcessInstanceId(
execution.getProcessInstanceId()
);
}
}
流程定义配置
| 节点 | 类型 | 表达式 | 说明 |
|---|
| 发起 | Start Event | - | 开始 |
| 审批 | User Task | leaveWorkflowBean.getLeaveApprovers(execution) | 审批人分配 |
| 判断 | Exclusive Gateway | leaveWorkflowBean.needsHrApproval(execution) | 是否需要 HR |
| HR审批 | User Task | 固定分配 | HR 最终审批 |
| 结束 | End Event | - | 完成 |